package org.datascooter.cglib.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.datascooter.bundle.ContainerProvider;
import org.datascooter.bundle.DSMapper;
import org.datascooter.bundle.EntityBundle;
import org.datascooter.cglib.proxy.impl.ProxyList;
import org.datascooter.cglib.proxy.impl.ProxyMap;
import org.datascooter.cglib.proxy.impl.ProxySet;
import org.datascooter.exception.EntityNotMappedException;
import org.datascooter.impl.DataSnip;
import org.datascooter.impl.DataSnipFactory;
import org.datascooter.inface.IDataManager;
import org.datascooter.utils.LangUtils;
import org.datascooter.utils.TypeUtils;

public class SimpleInterceptor implements MethodInterceptor {

	protected final IDataManager manager;
	protected Object container;
	protected ContainerProvider crossProvider;
	private Map<String, Object> itemMap = new HashMap<String, Object>();
	private SimpleInterceptor parent;
	protected String containerAttribute;

	public SimpleInterceptor(IDataManager dataManager, Object container) {
		this(null, container, null, dataManager);
	}

	public SimpleInterceptor(SimpleInterceptor parent, Object container, String containerAttribute, IDataManager dataManager) {
		this.parent = parent;
		crossProvider = dataManager.getInstanceManager().getCrossProvider();
		this.containerAttribute = containerAttribute;
		this.manager = dataManager;
		setContainer(container);
	}

	@Override
	public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if (method.getName().startsWith("set") || method.getName().startsWith("add") || method.getName().startsWith("put")) {
			return processSet(object, method, args, methodProxy);
		} else {
			return processGet(object, method, args, methodProxy);
		}
	}

	@SuppressWarnings("rawtypes")
	private Object processGet(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		String attributeName = LangUtils.prepareName(method.getName());
		Class<?> klazz = method.getReturnType();
		if (TypeUtils.isPrimitive(klazz)) {
			return method.invoke(getSource(), args);
		} else {
			Class<?> trueClass = LangUtils.resolveClass(method, klazz);
			if (klazz.equals(trueClass)) {
				List<Object> content = getContent(attributeName, args);
				if (content.size() == 0) {
					return null;
				}
				Object storedItem = itemMap.get(attributeName);
				if (storedItem == null) {
					storedItem = Enhancer.create(klazz, new SimpleInterceptor(manager, content.get(0)));
					itemMap.put(attributeName, storedItem);
				}
				return storedItem;
			} else {
				if (List.class.isAssignableFrom(klazz)) {
					Object storedItem = itemMap.get(attributeName);
					if (storedItem == null) {
						storedItem = Enhancer.create(klazz, new ProxyList(this, getSource(), attributeName, manager));
						itemMap.put(attributeName, storedItem);
					}
					return storedItem;
				} else if (Set.class.isAssignableFrom(klazz)) {
					Object storedItem = itemMap.get(attributeName);
					if (storedItem == null) {
						storedItem = Enhancer.create(klazz, new ProxySet(this, getSource(), attributeName, manager));
						itemMap.put(attributeName, storedItem);
					}
					return storedItem;
				} else if (Map.class.isAssignableFrom(klazz)) {
					Object storedItem = itemMap.get(attributeName);
					if (storedItem == null) {
						storedItem = Enhancer.create(klazz, new ProxyMap(this, getSource(), attributeName, manager));
						itemMap.put(attributeName, storedItem);
					}
					return storedItem;
				}
			}
		}
		return null;
	}

	@SuppressWarnings({
			"rawtypes", "unchecked"
	})
	private Object processSet(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		String attributeName = LangUtils.prepareName(method.getName());
		Object storedItem = itemMap.get(attributeName);
		if (args.length == 1) {
			if (TypeUtils.isPrimitive(args[0].getClass())) {
				EntityBundle bundle = DSMapper.getBundle(object);
				if (bundle.getAttribute(attributeName) == null) {
					return method.invoke(container, args);
				} else {
					saveChanges(attributeName, args);
				}
			} else {
				if (storedItem != null) {
					if (storedItem instanceof ProxyList) {
						((ProxyList) storedItem).add(args[0]);
					} else if (storedItem instanceof ProxySet) {
						((ProxySet) storedItem).add(args[0]);
					}
				}
				saveContent(attributeName, args);
			}
		} else if (args.length == 2) {
			if (TypeUtils.isPrimitive(args[0].getClass()) && !TypeUtils.isPrimitive(args[1].getClass())) {
				if (storedItem != null) {
					((ProxyMap) storedItem).put(args[0], args[1]);
				}
				saveContent(attributeName, args);
			} else {
				return method.invoke(container, args);
			}
		}
		return null;
	}

	protected void saveChanges(String attributeName, Object[] args) {
		DataSnip snip = DataSnipFactory.update(container);
		snip.what(attributeName, args[0]);
		manager.update(snip);
	}

	protected void saveContent(String attributeName, Object[] args) throws EntityNotMappedException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
		crossProvider.saveContent(container, attributeName, args);
	}

	protected List<Object> getContent(String attributeName, Object[] args) {
		return crossProvider.getContent(container, attributeName);
	}

	public SimpleInterceptor getParent() {
		return parent;
	}

	public String getContainerAttribute() {
		return containerAttribute;
	}

	public Object getContainer() {
		return container;
	}

	public Object getSource() {
		return container;
	}

	public void setContainer(Object container) {
		this.container = container;
	}
}
